package app

import (
	"context"
	"errors"
	"os"
	"os/signal"
	"syscall"
	"time"

	"github.com/sirupsen/logrus"
	"golang.org/x/sync/errgroup"
)

type App struct {
	opts   options
	ctx    context.Context
	cancel context.CancelFunc
}

func New(opts ...Option) *App {
	defaultOpt := options{
		ctx:         context.Background(),
		sigs:        []os.Signal{syscall.SIGTERM, syscall.SIGQUIT, syscall.SIGINT},
		stopTimeout: time.Second * 10,
	}
	for _, opt := range opts {
		opt(&defaultOpt)
	}

	ctx, cancel := context.WithCancel(defaultOpt.ctx)
	return &App{
		opts:   defaultOpt,
		ctx:    ctx,
		cancel: cancel,
	}
}

func (a *App) Run() error {
	eg, ectx := errgroup.WithContext(a.ctx)

	for _, srv := range a.opts.servers {
		srv := srv
		eg.Go(func() error {
			<-ectx.Done()
			stopCtx, cancel := context.WithTimeout(a.opts.ctx, a.opts.stopTimeout)
			defer cancel()
			return srv.Stop(stopCtx)
		})
		eg.Go(func() error {
			return srv.Start(a.ctx)
		})
	}

	sigChan := make(chan os.Signal, 1)
	signal.Notify(sigChan, a.opts.sigs...)
	eg.Go(func() error {
		select {
		case <-ectx.Done():
			return nil
		case sig := <-sigChan:
			logrus.Infof("app: received signal [ %s ] stopping app ...", sig.String())
			time.Sleep(time.Second)
			return a.Stop()
		}
	})
	if err := eg.Wait(); err != nil && !errors.Is(err, context.Canceled) {
		return err
	}

	return nil
}

func (a *App) Stop() error {
	if a.cancel != nil {
		a.cancel()
	}
	return nil
}
